package my.edu.model.bills.fund.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import my.edu.common.constant.MessageConstant;
import my.edu.common.constant.OrderConstant;
import my.edu.common.domain.PageDTO;
import my.edu.common.domain.PageVO;
import my.edu.common.exception.BillsException;
import my.edu.common.utils.PageUtils;
import my.edu.constant.BillsType;
import my.edu.constant.FoundBussinessType;
import my.edu.constant.OrderNoConstant;
import my.edu.model.bills.fund.entity.OtherExpenseOrder;
import my.edu.model.bills.fund.entity.OtherExpenseOrderDetail;

import my.edu.model.bills.fund.entity.OtherIncomeOrderDetail;
import my.edu.model.bills.fund.entity.dto.*;
import my.edu.model.bills.fund.entity.vo.ExpenseDetailVO;
import my.edu.model.bills.fund.entity.vo.ExpenseVO;
import my.edu.model.bills.fund.entity.vo.IncomeDetailVO;
import my.edu.model.bills.fund.entity.vo.IncomeVO;
import my.edu.model.information.service.IAccountService;
import my.edu.utils.ChildrenLoader;
import my.edu.utils.OrderNoUtil;
import my.edu.utils.OrderServiceUtil;
import my.edu.websocket.BillWebSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import my.edu.model.bills.fund.mapper.OtherExpenseOrderMapper;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@Service
public class OtherExpenseOrderServiceImpl extends ServiceImpl<OtherExpenseOrderMapper, OtherExpenseOrder> implements IOtherExpenseOrderService{


    @Autowired
    private OrderNoConstant orderNoConstant;
    @Autowired
    private IOtherExpenseOrderDetailService otherExpenseOrderDetailService;
    @Autowired
    private IAccountService accountService;
    @Autowired
    private OrderServiceUtil orderServiceUtil;
    @Autowired
    private FoundBussinessType businessType;
    @Autowired
    private BillWebSocket billWebSocket;

    @Override
    public void saveNew(ExpenseDTO expenseDTO) {
        checkDTO(expenseDTO, true);
        OtherExpenseOrder otherExpenseOrder = BeanUtil.copyProperties(expenseDTO, OtherExpenseOrder.class, "expenseDetailDTOList");
        setField(otherExpenseOrder, expenseDTO.getExpenseDetailDTOList());
        save(otherExpenseOrder);
        otherExpenseOrderDetailService.saveBatchByDTO(expenseDTO.getExpenseDetailDTOList(), otherExpenseOrder);
        billWebSocket.sendToAllClient(BillsType.OTHEREXPENSE,"单据类型：支出单;单据编号："+otherExpenseOrder.getOrderNo()+";提交人："+otherExpenseOrder.getCreatedName());

    }

    private void setField(OtherExpenseOrder otherExpenseOrder, List<ExpenseDetailDTO>  expenseDetailDTOList) {
        BigDecimal amount = BigDecimal.ZERO;
        for (ExpenseDetailDTO expenseDetailDTO : expenseDetailDTOList) {
            amount = amount.add(expenseDetailDTO.getAmount());
        }
        BigDecimal debt = amount.subtract(otherExpenseOrder.getAmountPaid());
        otherExpenseOrder.setPaymentAmount(amount);
        otherExpenseOrder.setDebtAmount(debt);
        if (debt.compareTo(BigDecimal.ZERO) == 0) {
            otherExpenseOrder.setPaymentStatus(OrderConstant.GET_PAYMENT);
            otherExpenseOrder.setOrderDate(LocalDate.now());
        }
        else if (debt.compareTo(amount) == 0){
            otherExpenseOrder.setPaymentStatus(OrderConstant.NOT_PAYMENT);
            otherExpenseOrder.setOrderDate(null);
        }
        else {
            otherExpenseOrder.setPaymentStatus(OrderConstant.PART_PAYMENT);
            otherExpenseOrder.setOrderDate(null);
        }
        otherExpenseOrder.setIsAudited(0);
    }

    private void checkDTO(ExpenseDTO expenseDTO, boolean isNew) {
        if (expenseDTO.getOrderNo() == null || expenseDTO.getOrderNo().isEmpty()) {
            //设置No
            String orderNo = OrderNoUtil.generateOrderNo(orderNoConstant.getOtherExpenseNo());
            expenseDTO.setOrderNo(orderNo);
        } else {
            //检查是否有重复no
            long count = count(Wrappers.<OtherExpenseOrder>lambdaQuery().eq(OtherExpenseOrder::getOrderNo, expenseDTO.getOrderNo()));
            if (isNew && count > 0) throw new BillsException(MessageConstant.ORDER_NO_EXISTS);
        }
        orderServiceUtil.checkDTO(null, expenseDTO.getSettlementAccountId(), expenseDTO.getSalespersonId(), expenseDTO.getSupplierId(),null);
        List<String> boundBusinessType = businessType.getExpenseBusinessType();
        for (ExpenseDetailDTO inComeDetailDTO : expenseDTO.getExpenseDetailDTOList()) {
            boolean contains = boundBusinessType.contains(inComeDetailDTO.getExpenseCategory());
            if (!contains) throw new BillsException(MessageConstant.BOUND_BUSINESS_TYPE_NOT_FOUND);
        }
    }

    @Override
    public void updateByDTO(ExpenseDTO expenseDTO) {
        OtherExpenseOrder byId = getById(expenseDTO.getId());
        if (byId == null) throw new BillsException(MessageConstant.ORDER_NOT_EXIST);
        if (byId.getIsAudited() != null && byId.getIsAudited() == 1)
            throw new BillsException(MessageConstant.ISAUDIT_CAN_NOT_UPDATE);
        checkDTO(expenseDTO, false);
        BeanUtil.copyProperties(expenseDTO, byId, "incomeDetailDTOList");
        setField(byId, expenseDTO.getExpenseDetailDTOList());
        updateById(byId);
        otherExpenseOrderDetailService.updateByDTO(expenseDTO.getExpenseDetailDTOList(), byId);
        billWebSocket.sendToAllClient(BillsType.OTHEREXPENSE,"更新;单据类型：支出单;单据编号："+byId.getOrderNo()+";提交人："+byId.getUpdateName());
    }


    @Override
    @Transactional
    public void removeOrder(List<Long> longs) {
        List<OtherExpenseOrder> otherExpenseOrders = listByIds(longs);
        for (OtherExpenseOrder order : otherExpenseOrders) {
            if (order.getIsAudited() != null && order.getIsAudited() == 1)
                throw new BillsException(order.getOrderNo() + MessageConstant.ISAUDIT_CAN_NOT_DEL);
        }
        removeBatchByIds(longs);
        otherExpenseOrderDetailService.removeByOrderId(longs);
    }


    @Override
    public void auditOrder(List<Long> longs) {
        updateAudit(longs, true);
    }

    private void updateAudit(List<Long> longs, boolean isAudit) {
        List<OtherExpenseOrder> list = list(Wrappers.<OtherExpenseOrder>lambdaQuery().in(OtherExpenseOrder::getId, longs));
        for (OtherExpenseOrder order : list) {
            if (isAudit) {
                if (order.getIsAudited() != null && order.getIsAudited() == 1)
                    throw new BillsException(order.getOrderNo() + MessageConstant.ISAUDITED);
            } else if (order.getIsAudited() != null && order.getIsAudited() == 0) {
                throw new BillsException(order.getOrderNo() + MessageConstant.NOTAUDITED);
            }
        }
        Map<Long, BigDecimal> map = getAccountIdToTotalAmountMap(list);
        if (isAudit) {
            orderServiceUtil.setAuditStatus(this, longs, true);
            accountService.subBalance(map);
        } else {
            orderServiceUtil.setAuditStatus(this, longs, false);
            accountService.addBalance(map);
        }
    }

    private Map<Long, BigDecimal> getAccountIdToTotalAmountMap(List<OtherExpenseOrder> list) {
        return list.stream()
                .collect(Collectors.toMap(
                        OtherExpenseOrder::getSettlementAccountId,
                        i -> i.getAmountPaid()
                ));
    }

    @Override
    public void rauditOrder(List<Long> longs) {
        updateAudit(longs, false);
    }


    @Override
    public PageVO<ExpenseVO> getOrderList(PageDTO page, ExpenseSearchDTO searchDTO) {
        MPJLambdaWrapper<OtherExpenseOrder> wrapper = new MPJLambdaWrapper<>();
        wrapper
                .selectAsClass(OtherExpenseOrder.class, ExpenseVO.class)
                .like(searchDTO.getOrderNo() != null && !searchDTO.getOrderNo().isEmpty(),
                        OtherExpenseOrder::getOrderNo, searchDTO.getOrderNo())
                .between(searchDTO.getBeginTime() != null,
                        OtherExpenseOrder::getOrderDate, searchDTO.getBeginTime(), searchDTO.getEndTime())
                .eq(searchDTO.getIsAudited() != null,
                        OtherExpenseOrder::getIsAudited, searchDTO.getIsAudited())
                .eq(searchDTO.getSalesmanId() != null,
                        OtherExpenseOrder::getSalespersonId, searchDTO.getSalesmanId())
                .eq(searchDTO.getAuditorId() != null,
                        OtherExpenseOrder::getAuditorId, searchDTO.getAuditorId())
                .eq(searchDTO.getSettlementAccountId() != null,
                        OtherExpenseOrder::getSettlementAccountId, searchDTO.getSettlementAccountId())
                .eq(searchDTO.getPaymentStatus() != null,
                        OtherExpenseOrder::getPaymentStatus, searchDTO.getPaymentStatus())
                .orderByDesc(OtherExpenseOrder::getCreateTime);
        Page<ExpenseVO> expenseVOPage = baseMapper.selectJoinPage(new Page<>(page.getPage(), page.getSize()), ExpenseVO.class, wrapper);
        if (CollUtil.isNotEmpty(expenseVOPage.getRecords())) {
            ChildrenLoader.loadAndFillDTO(
                    expenseVOPage.getRecords(),
                    ExpenseVO::getId,
                    ids -> otherExpenseOrderDetailService.list(
                            Wrappers.<OtherExpenseOrderDetail>lambdaQuery().in(OtherExpenseOrderDetail::getOtherExpenseOrderId, ids)),
                    ExpenseDetailVO::getOtherExpenseOrderId,
                    ExpenseDetailVO.class,
                    ExpenseVO::setExpenseDetailVOList);
        };
        return PageUtils.toPage(expenseVOPage);
    }

    @Override
    public Object getOrderOne(Long id) {
        MPJLambdaWrapper<OtherExpenseOrder> wrapper=new MPJLambdaWrapper<>();
        wrapper
                .selectAsClass(OtherExpenseOrder.class, ExpenseVO.class)
                .selectCollection(OtherExpenseOrderDetail.class, ExpenseVO::getExpenseDetailVOList)
                .leftJoin(OtherExpenseOrderDetail.class, OtherExpenseOrderDetail::getOtherExpenseOrderId, OtherExpenseOrder::getId)
                .eq(OtherExpenseOrder::getId, id);
        return baseMapper.selectJoinOne(ExpenseVO.class, wrapper);
    }
}
